#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "rmath.h"
#include "redintobject.h"
#include "redboolobject.h"
#include "redfloatobject.h"
#include "redstringobject.h"
#include "redexceptionobject.h"

static RedObject *double_repr(RedObject *self)
{
	return RedString_FromCFormat("%.2lf", RedFloat_Value(self));
}

static uint32_t double_hash(RedObject *self)
{
	return rmath_hash_d64(RedFloat_Value(self));
}

#define apply_unary(op)			{ return RedFloat_FromFloat(op RedFloat_Value(self)); }

#define apply_binary(op)																\
	{																					\
		if (RedInt_Exact(other))														\
			return RedFloat_FromFloat(RedFloat_Value(self) op RedInt_Value(other));		\
																						\
		if (RedFloat_Exact(other))														\
			return RedFloat_FromFloat(RedFloat_Value(self) op RedFloat_Value(other));	\
																						\
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'float' expected");		\
		return NULL;																	\
	}

#define apply_binary_bool(op)															\
	{																					\
		if (RedInt_Exact(other))														\
		{																				\
			if (RedFloat_Value(self) op RedInt_Value(other))							\
				Red_Return_True();														\
			else																		\
				Red_Return_False();														\
		}																				\
																						\
		if (RedFloat_Exact(other))														\
		{																				\
			if (RedFloat_Value(self) op RedFloat_Value(other))							\
				Red_Return_True();														\
			else																		\
				Red_Return_False();														\
		}																				\
																						\
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'float' expected");		\
		return NULL;																	\
	}

#define apply_binary_relation(op)														\
	{																					\
		if ((RedFloat_Value(self) == 0.0 ? 0 : 1) op RedBool_IsTrue(other))				\
			Red_Return_True();															\
		else																			\
			Red_Return_False();															\
	}

static RedObject *double_pos(RedObject *self)							apply_unary(+)
static RedObject *double_neg(RedObject *self)							apply_unary(-)

static RedObject *double_add(RedObject *self, RedObject *other)			apply_binary(+)
static RedObject *double_sub(RedObject *self, RedObject *other)			apply_binary(-)
static RedObject *double_mul(RedObject *self, RedObject *other)			apply_binary(*)

static RedObject *double_bool_or(RedObject *self, RedObject *other)		apply_binary_relation(||)
static RedObject *double_bool_and(RedObject *self, RedObject *other)	apply_binary_relation(&&)
static RedObject *double_bool_xor(RedObject *self, RedObject *other)	apply_binary_relation(!=)

static RedObject *double_le(RedObject *self, RedObject *other)			apply_binary_bool(<)
static RedObject *double_ge(RedObject *self, RedObject *other)			apply_binary_bool(>)
static RedObject *double_leq(RedObject *self, RedObject *other)			apply_binary_bool(<=)
static RedObject *double_geq(RedObject *self, RedObject *other)			apply_binary_bool(>=)

#undef apply_unary
#undef apply_binary
#undef apply_binary_bool
#undef apply_binary_relation

static RedObject *double_div(RedObject *self, RedObject *other)
{
	double a = RedFloat_Value(self);

	if (RedInt_Exact(other))
	{
		int64_t b = RedInt_Value(other);

		if (b != 0)
			return RedFloat_FromFloat(a / b);

		RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
		return NULL;
	}
	else if (RedFloat_Exact(other))
	{
		double b = RedFloat_Value(other);

		if (b != 0.0)
			return RedFloat_FromFloat(a / b);

		RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
		return NULL;
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'double' expected");
		return NULL;
	}
}

static RedObject *double_pow(RedObject *self, RedObject *other)
{
	double a = RedFloat_Value(self);

	if (a < 0.0)
	{
		RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
		return NULL;
	}

	if (a == 0.0)
		return RedFloat_FromFloat(0.0);

	if (RedInt_Exact(other))
	{
		int64_t b = RedInt_Value(other);

		if (b >= 0)
			return RedFloat_FromFloat(rmath_pow_d64(a, b));
		else
			return RedFloat_FromFloat(rmath_pow_d64_2(a, b));
	}
	else if (RedFloat_Exact(other))
	{
		double b = RedFloat_Value(other);

		if (b == 0.0)
			return RedFloat_FromFloat(1.0);
		else
			return RedFloat_FromFloat(rmath_pow_d64_2(a, b));
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'double' expected");
		return NULL;
	}
}

static RedObject *double_bool_not(RedObject *self)
{
	if (RedFloat_Value(self) == 0.0)
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *double_eq(RedObject *self, RedObject *other)
{
	if ((RedInt_Exact(other) && (RedFloat_Value(self) == RedInt_Value(other))) ||
		(RedFloat_Exact(other) && (RedFloat_Value(self) == RedFloat_Value(other))))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *double_neq(RedObject *self, RedObject *other)
{
	if ((!RedInt_Exact(other) || (RedFloat_Value(self) != RedInt_Value(other))) &&
		(!RedFloat_Exact(other) || (RedFloat_Value(self) != RedFloat_Value(other))))
		Red_Return_True();
	else
		Red_Return_False();
}

RedTypeObject RedType_Float =
{
	RedType_INIT("float", RedFloatObject, RedType_Object)

	.tp_str			= double_repr,
	.tp_repr		= double_repr,
	.tp_hash		= double_hash,

	.tp_add			= double_add,
	.tp_sub			= double_sub,
	.tp_mul			= double_mul,
	.tp_div			= double_div,
	.tp_pow			= double_pow,

	.tp_aug_add		= double_add,
	.tp_aug_sub		= double_sub,
	.tp_aug_mul		= double_mul,
	.tp_aug_div		= double_div,
	.tp_aug_pow		= double_pow,

	.tp_bool_or		= double_bool_or,
	.tp_bool_and	= double_bool_and,
	.tp_bool_xor	= double_bool_xor,
	.tp_bool_not	= double_bool_not,

	.tp_eq			= double_eq,
	.tp_le			= double_le,
	.tp_ge			= double_ge,
	.tp_neq			= double_neq,
	.tp_leq			= double_leq,
	.tp_geq			= double_geq,

	.tp_pos			= double_pos,
	.tp_neg			= double_neg,
};

RedObject *RedFloat_FromInt(int64_t value)
{
	return RedFloat_FromFloat((double)value);
}

RedObject *RedFloat_FromFloat(double value)
{
	RedObject *result = RedObject_Create(&RedType_Float, NULL);

	if (!result)
		return NULL;

	RedFloat_Value(result) = value;
	return result;
}

RedObject *RedFloat_FromBool(RedObject *value)
{
	return RedFloat_FromInt(RedBool_IsTrue(value) ? 1 : 0);
}

RedObject *RedFloat_FromString(RedObject *value)
{
	return RedFloat_FromCString(RedString_Buffer(value));
}

RedObject *RedFloat_FromCString(const char *value)
{
	char *tailptr;
	double result = strtod(value, &tailptr);

	if (result == 0.0 && (errno != 0 || *tailptr != '\0'))
	{
		RedException_ThrowErrno(&RedExc_ValueError);
		return NULL;
	}

	return RedFloat_FromInt(result);
}
